<?php
namespace org\FoxNet;
use RuntimeException;
use Exception;

class Core extends \WebSocket\Client {

  const UUID = '511C9B89D558D7BAAD20B6090902302B';  // 固定
  const USID = 5271916; // 固定
  const USUS = 25000; // usleep一次多少微秒
  const UMAX = 120; // usleep多少次

  public function __construct($wsid) {

    $uri = config("ws.{$wsid}");
    if (empty($uri)) {
      throw new RuntimeException("连接参数无效{$wsid}");
    }

    parent::__construct($uri, [
      'headers' => [
        'origin' => 'http://127.0.0.1', // !!!必填
      ],
      'timeout_connect' => 5,
      'timeout_stream_s' => 0,
      'timeout_stream_ms' => self::USUS,
    ]);

    $finsh = false;
    for ($i = 0; $i < self::UMAX; $i++) {
      $msgs = $this->RecvMsgs(); // 连接&&读取代理验证
      foreach($msgs as $msg) {
        $MCmdID = $msg['head']['MainCmdID'];
        $SCmdID = $msg['head']['SubCmdID'];
        if ($MCmdID == 0 && $SCmdID == 2) {  // MDM_KN_COMMAND && SUB_KN_VALIDATE_SOCKET
          $eax = unpack('CTransType/VTimeStamp', $msg['body']);
          $TimeStamp = (string)$eax['TimeStamp'];
          $TransType = $eax['TransType'];
          $chg = ($TimeStamp[$TransType] + 48) * $TransType + 10;
          $aaa = substr($TimeStamp, 0, $TransType);
          $bbb = substr($TimeStamp, $TransType);
          $ccc = $aaa.$chg.$bbb.self::UUID;
          $ccc = md5($ccc);
          $buf = pack('a66Va66',
            self::UTF8_UNICODE(self::UUID),
            self::USID,
            self::UTF8_UNICODE($ccc)
          );
          $this->SendData(0, 2, $buf);  // MDM_KN_COMMAND SUB_KN_VALIDATE_SOCKET 代理验证回复
          $finsh = true;
          break;
        }
      }
      if ($finsh) {
        break;
      }
      usleep(self::USUS);
    }

  }
  
  /**
   *  @brief  : 解析接收到的二进制数据, 返回若干个消息包
   *  @return : array/RuntimeException
   *  @details:
   */
  public function RecvMsgs() {
    $list = [];
    $recv = '';
    do {  // 接收多个消息包
      try {
        $data = $this->receive(); // 接收1个消息包
        $recv = $recv.$data;  // 拼接若干个消息包
      } catch (Exception $e) {
        $error = $e->getMessage();
        $errno = $e->getCode();
      }
      $meta = stream_get_meta_data($this->socket);
      $feof = feof($this->socket);
      $tout = $meta['timed_out'];
      if (!(!$feof && !$tout)) {  // 一直读到超时为止
        break;
      }
    } while (true);

    $TCP_UNPACK = 'CDataKind/CCheckCode/vPacketSize/VMessageID/vMainCmdID/vSubCmdID';
    do { // 解析多个消息包
      $len = strlen($recv);
      if ($len == 0) {
        break;
      }
      if ($len < 12) {
        throw new RuntimeException("数据长度至少为12, 当前{$len}");
      }
      // 包头固定12字节
      $head = unpack($TCP_UNPACK, $recv);
      if ($head['PacketSize'] < 12 || $head['PacketSize'] > 16384 || $len < $head['PacketSize']) {
        throw new RuntimeException("数据长度不符合, 当前{$head['PacketSize']}");
      }

      if ($len < $head['PacketSize']) {
        throw new RuntimeException("消息长度不符合, 总长={$len}, 包长={$head['PacketSize']}");
      }

      $data = substr($recv, 0, $head['PacketSize']);
      $recv = substr($recv, $head['PacketSize']);
      // 字节码映射
      if ($head['DataKind'] != 0) {
        $CheckCode = $head['CheckCode'];
        for ($i = 8; $i < $head['PacketSize']; $i++) {
          $code = ord($data[$i]);
          $code = self::$s_RecvByteMap[$code];
          $CheckCode += $code;
          $CheckCode %= 256;
          $data[$i] = chr($code);
        }
        if ($CheckCode != 0) {
          throw new RuntimeException('数据映射失败');
        }
        $head = unpack($TCP_UNPACK, $data);
      }
      if ($head['MainCmdID'] == 0 && $head['SubCmdID'] == 1) {
        continue;
      }
      $retn = ['head' => $head, 'body' => substr($data, 12)];
      $list[] = $retn;
    } while(true);
    return $list;
  }

  /**
   *  @brief : 统一发包
   *  @param : [in] $MainCmdID : 主命令
   *  @param : [in] $SubCmdID  : 子命令
   *  @param : [in] $data    : 包体二进制
   *  @return: none
   *  @detail:
   */
  public function SendData($MainCmdID, $SubCmdID, $data = '') {

    $bin = pack('CCvVvva*',
      1, // DataKind  byte   //数据类型
      0, // CheckCode byte   //效验字段
      12 + strlen($data), // PacketSize uint16 //数据大小
      0, // MessageID uint32 //转发端口
      $MainCmdID, // MainCmdID uint16 //主命令码
      $SubCmdID, // SubCmdID  uint16 //子命令码
      $data
    );

    $CheckCode = 0;
    $len = strlen($bin);
    for ($i = 8; $i < $len; $i++) {
      $eax = ord($bin[$i]);
      $CheckCode += $eax;
      $CheckCode %= 256;
      $eax = self::$s_SendByteMap[$eax];
      $bin[$i] = chr($eax);
    }
    $CheckCode = ~$CheckCode + 1;
    $bin[1] = chr($CheckCode);

    $this->send($bin, 'binary');
  }

  // UNICODEFFFE UTF-16LE UTF16LE  => CP65001 UTF-8 UTF8
  protected static function UTF8_UNICODE($str){
    return iconv('UTF-8', 'UTF-16LE', $str);
  }
  
  protected static function UNICODE_UTF8($str){
    return iconv('UTF-16LE', 'UTF-8', $str);
  }
  
  private static $s_RecvByteMap = [
    0x51, 0xA1, 0x9E, 0xB0, 0x1E, 0x83, 0x1C, 0x2D, 0xE9, 0x77, 0x3D, 0x13, 0x93, 0x10, 0x45, 0xFF,
    0x6D, 0xC9, 0x20, 0x2F, 0x1B, 0x82, 0x1A, 0x7D, 0xF5, 0xCF, 0x52, 0xA8, 0xD2, 0xA4, 0xB4, 0x0B,
    0x31, 0x97, 0x57, 0x19, 0x34, 0xDF, 0x5B, 0x41, 0x58, 0x49, 0xAA, 0x5F, 0x0A, 0xEF, 0x88, 0x01,
    0xDC, 0x95, 0xD4, 0xAF, 0x7B, 0xE3, 0x11, 0x8E, 0x9D, 0x16, 0x61, 0x8C, 0x84, 0x3C, 0x1F, 0x5A,
    0x02, 0x4F, 0x39, 0xFE, 0x04, 0x07, 0x5C, 0x8B, 0xEE, 0x66, 0x33, 0xC4, 0xC8, 0x59, 0xB5, 0x5D,
    0xC2, 0x6C, 0xF6, 0x4D, 0xFB, 0xAE, 0x4A, 0x4B, 0xF3, 0x35, 0x2C, 0xCA, 0x21, 0x78, 0x3B, 0x03,
    0xFD, 0x24, 0xBD, 0x25, 0x37, 0x29, 0xAC, 0x4E, 0xF9, 0x92, 0x3A, 0x32, 0x4C, 0xDA, 0x06, 0x5E,
    0x00, 0x94, 0x60, 0xEC, 0x17, 0x98, 0xD7, 0x3E, 0xCB, 0x6A, 0xA9, 0xD9, 0x9C, 0xBB, 0x08, 0x8F,
    0x40, 0xA0, 0x6F, 0x55, 0x67, 0x87, 0x54, 0x80, 0xB2, 0x36, 0x47, 0x22, 0x44, 0x63, 0x05, 0x6B,
    0xF0, 0x0F, 0xC7, 0x90, 0xC5, 0x65, 0xE2, 0x64, 0xFA, 0xD5, 0xDB, 0x12, 0x7A, 0x0E, 0xD8, 0x7E,
    0x99, 0xD1, 0xE8, 0xD6, 0x86, 0x27, 0xBF, 0xC1, 0x6E, 0xDE, 0x9A, 0x09, 0x0D, 0xAB, 0xE1, 0x91,
    0x56, 0xCD, 0xB3, 0x76, 0x0C, 0xC3, 0xD3, 0x9F, 0x42, 0xB6, 0x9B, 0xE5, 0x23, 0xA7, 0xAD, 0x18,
    0xC6, 0xF4, 0xB8, 0xBE, 0x15, 0x43, 0x70, 0xE0, 0xE7, 0xBC, 0xF1, 0xBA, 0xA5, 0xA6, 0x53, 0x75,
    0xE4, 0xEB, 0xE6, 0x85, 0x14, 0x48, 0xDD, 0x38, 0x2A, 0xCC, 0x7F, 0xB1, 0xC0, 0x71, 0x96, 0xF8,
    0x3F, 0x28, 0xF2, 0x69, 0x74, 0x68, 0xB7, 0xA3, 0x50, 0xD0, 0x79, 0x1D, 0xFC, 0xCE, 0x8A, 0x8D,
    0x2E, 0x62, 0x30, 0xEA, 0xED, 0x2B, 0x26, 0xB9, 0x81, 0x7C, 0x46, 0x89, 0x73, 0xA2, 0xF7, 0x72,
  ];

  private static $s_SendByteMap = [
    0x70, 0x2F, 0x40, 0x5F, 0x44, 0x8E, 0x6E, 0x45, 0x7E, 0xAB, 0x2C, 0x1F, 0xB4, 0xAC, 0x9D, 0x91,
    0x0D, 0x36, 0x9B, 0x0B, 0xD4, 0xC4, 0x39, 0x74, 0xBF, 0x23, 0x16, 0x14, 0x06, 0xEB, 0x04, 0x3E,
    0x12, 0x5C, 0x8B, 0xBC, 0x61, 0x63, 0xF6, 0xA5, 0xE1, 0x65, 0xD8, 0xF5, 0x5A, 0x07, 0xF0, 0x13,
    0xF2, 0x20, 0x6B, 0x4A, 0x24, 0x59, 0x89, 0x64, 0xD7, 0x42, 0x6A, 0x5E, 0x3D, 0x0A, 0x77, 0xE0,
    0x80, 0x27, 0xB8, 0xC5, 0x8C, 0x0E, 0xFA, 0x8A, 0xD5, 0x29, 0x56, 0x57, 0x6C, 0x53, 0x67, 0x41,
    0xE8, 0x00, 0x1A, 0xCE, 0x86, 0x83, 0xB0, 0x22, 0x28, 0x4D, 0x3F, 0x26, 0x46, 0x4F, 0x6F, 0x2B,
    0x72, 0x3A, 0xF1, 0x8D, 0x97, 0x95, 0x49, 0x84, 0xE5, 0xE3, 0x79, 0x8F, 0x51, 0x10, 0xA8, 0x82,
    0xC6, 0xDD, 0xFF, 0xFC, 0xE4, 0xCF, 0xB3, 0x09, 0x5D, 0xEA, 0x9C, 0x34, 0xF9, 0x17, 0x9F, 0xDA,
    0x87, 0xF8, 0x15, 0x05, 0x3C, 0xD3, 0xA4, 0x85, 0x2E, 0xFB, 0xEE, 0x47, 0x3B, 0xEF, 0x37, 0x7F,
    0x93, 0xAF, 0x69, 0x0C, 0x71, 0x31, 0xDE, 0x21, 0x75, 0xA0, 0xAA, 0xBA, 0x7C, 0x38, 0x02, 0xB7,
    0x81, 0x01, 0xFD, 0xE7, 0x1D, 0xCC, 0xCD, 0xBD, 0x1B, 0x7A, 0x2A, 0xAD, 0x66, 0xBE, 0x55, 0x33,
    0x03, 0xDB, 0x88, 0xB2, 0x1E, 0x4E, 0xB9, 0xE6, 0xC2, 0xF7, 0xCB, 0x7D, 0xC9, 0x62, 0xC3, 0xA6,
    0xDC, 0xA7, 0x50, 0xB5, 0x4B, 0x94, 0xC0, 0x92, 0x4C, 0x11, 0x5B, 0x78, 0xD9, 0xB1, 0xED, 0x19,
    0xE9, 0xA1, 0x1C, 0xB6, 0x32, 0x99, 0xA3, 0x76, 0x9E, 0x7B, 0x6D, 0x9A, 0x30, 0xD6, 0xA9, 0x25,
    0xC7, 0xAE, 0x96, 0x35, 0xD0, 0xBB, 0xD2, 0xC8, 0xA2, 0x08, 0xF3, 0xD1, 0x73, 0xF4, 0x48, 0x2D,
    0x90, 0xCA, 0xE2, 0x58, 0xC1, 0x18, 0x52, 0xFE, 0xDF, 0x68, 0x98, 0x54, 0xEC, 0x60, 0x43, 0x0F,
  ];
  
}
